package session

import (
	"crypto/aes"
	"crypto/cipher"
	"encoding/json"
	"errors"
	"fmt"
	"gitee.com/zhucheer/orange/database"
	"github.com/gomodule/redigo/redis"
	"net/http"
	"sync"
)

var redisProvider = &RedisProvider{}
var redisConfigName = "default"

// RedisSessionStore session存储到redis中
type RedisSessionStore struct {
	sid    string
	values map[interface{}]interface{} // session data
	lock   sync.RWMutex
}

func (st *RedisSessionStore) Set(key, value interface{}) (err error) {
	st.lock.Lock()
	defer st.lock.Unlock()
	st.values[key] = value

	return nil
}

func (st *RedisSessionStore) Get(key interface{}) (value interface{}) {
	st.lock.RLock()
	defer st.lock.RUnlock()

	if v, ok := st.values[key]; ok {
		return v
	}
	return nil
}

func (st *RedisSessionStore) Delete(key interface{}) (err error) {
	st.lock.Lock()
	defer st.lock.Unlock()
	delete(st.values, key)
	return nil
}

func (st *RedisSessionStore) SessionID() (sid string) {
	return st.sid
}

func (st *RedisSessionStore) SessionRelease(w http.ResponseWriter) (err error) {
	encodedSessionInfo, err := encodeSessionInfo(redisProvider.block, redisProvider.config.SecurityKey, redisProvider.config.SecurityName, st.values)
	if err != nil {
		return err
	}

	db, put, err := database.GetRedis(redisConfigName)
	if err != nil {
		return err
	}
	defer database.PutConn(put)
	keyName := fmt.Sprintf("%s:%s", redisProvider.config.Name, st.sid)
	_, err = db.Do("SETEX", keyName, redisProvider.maxlifetime, encodedSessionInfo)
	if err != nil {
		return err
	}
	return nil
}

func (st *RedisSessionStore) Flush() (err error) {
	st.lock.Lock()
	defer st.lock.Unlock()
	st.values = make(map[interface{}]interface{})
	return nil
}

type RedisProvider struct {
	maxlifetime int64
	config      *redisConfig
	block       cipher.Block
}

type redisConfig struct {
	Name         string `json:"name"`
	BlockKey     string `json:"blockKey"`
	SecurityName string `json:"securityName"`
	SecurityKey  string `json:"securityKey"`
	Secure       bool   `json:"secure"`
}

func (pder *RedisProvider) SessionInit(maxlifetime int64, config string) error {
	pder.config = &redisConfig{}
	err := json.Unmarshal([]byte(config), pder.config)
	if err != nil {
		return err
	}

	if pder.config.BlockKey == "" {
		pder.config.BlockKey = string(generateRandomKey(16))
	}
	if pder.config.SecurityName == "" {
		pder.config.SecurityName = string(generateRandomKey(20))
	}
	pder.block, err = aes.NewCipher([]byte(pder.config.BlockKey))
	if err != nil {
		return err
	}
	pder.maxlifetime = maxlifetime
	return nil
}

func (pder *RedisProvider) SessionRead(sid string, r *http.Request) (Store, error) {
	db, put, err := database.GetRedis(redisConfigName)
	if err != nil {
		return nil, errors.New(fmt.Sprintf("session driver redis connect error:%v", err))
	}
	defer database.PutConn(put)
	keyName := fmt.Sprintf("%s:%s", pder.config.Name, sid)
	var maps map[interface{}]interface{}
	maps = make(map[interface{}]interface{})
	sessionInfo, err := redis.String(db.Do("GET", keyName))

	if err != nil {
		maps = make(map[interface{}]interface{})
		return &RedisSessionStore{sid: sid, values: maps}, err
	}

	maps, err = decodeSessionInfo(pder.block,
		pder.config.SecurityKey,
		pder.config.SecurityName,
		sessionInfo, pder.maxlifetime)

	if err != nil {
		maps = make(map[interface{}]interface{})
		return &RedisSessionStore{sid: sid, values: maps}, err
	}

	if maps == nil {
		maps = make(map[interface{}]interface{})
	}
	rs := &RedisSessionStore{sid: sid, values: maps}
	return rs, nil
}

// SessionExist Redis session is always existed
func (pder *RedisProvider) SessionExist(sid string) bool {
	return true
}

// SessionRegenerate Implement method, no used.
func (pder *RedisProvider) SessionRegenerate(oldsid, sid string) (Store, error) {
	return nil, nil
}

// SessionDestroy Implement method, no used.
func (pder *RedisProvider) SessionDestroy(sid string) error {
	return nil
}

// SessionGC Implement method, no used.
func (pder *RedisProvider) SessionGC() {
}

// SessionAll Implement method, return 0.
func (pder *RedisProvider) SessionAll() int {
	return 0
}
